home *** CD-ROM | disk | FTP | other *** search
/ Deutsche Edition 1 / Deutsche Edition 1.iso / amok / amok_lha / amok71.lha / XStat / XStat.mod < prev    next >
Text File  |  1993-08-15  |  34KB  |  1,159 lines

  1. (***************************************************************************
  2.  :Program.    XStat
  3.  :Author.     Jürgen Weinelt
  4.  :Address.    Zur Kanzel 1, D-8783 Hammelburg, Germany
  5.  :Version.    1.03 (02-Apr-92)
  6.  :Copyright.  Freeware
  7.  :Language.   Modula-2
  8.  :Translator. M2Amiga V4.097d
  9.  :History.    1.00 initial release
  10.  :History.    1.01 bug fix: used to guru when xfer = 0 bytes
  11.  :History.    1.02 added peak cps rating
  12.  :History.    1.03 added monthly statistics
  13.  :Contents.   Extract statistics from UUCiCo "Xferstat" logfile
  14.  :Contents.   Requires UUCiCo 1.15c (or later) by Andrew "Charly" Kopp
  15.  **************************************************************************)
  16.  
  17.  
  18.  
  19. MODULE XStat;
  20. (*$ StackChk:=FALSE *)
  21. (*$ CaseChk:=FALSE *)
  22. (*$ Volatile:=FALSE *)
  23.  
  24.  
  25.  
  26. IMPORT Arts;
  27. IMPORT ASCII;
  28. IMPORT Break;
  29. IMPORT DosD;
  30. IMPORT DosL;
  31. IMPORT ExecD;
  32. IMPORT ExecL;
  33. IMPORT InOut;
  34. IMPORT R;
  35. IMPORT RealConversions;
  36. IMPORT RealInOut;
  37. IMPORT String;
  38.  
  39. IMPORT SYSTEM;
  40.  
  41.  
  42.  
  43. CONST
  44.  strlen=100;
  45.  
  46.  dmult=24*60*60;
  47.  hmult=60*60;
  48.  mmult=60;
  49.  smult=1;
  50.  dsun=0*dmult;
  51.  dmon=1*dmult;
  52.  dtue=2*dmult;
  53.  dwed=3*dmult;
  54.  dthu=4*dmult;
  55.  dfri=5*dmult;
  56.  dsat=6*dmult;
  57.  
  58.  defxstatdata="UULIB:XStat.data";
  59.  defxferstat="UUSPOOL:XferStat";
  60.  
  61.  vers="XStat 1.03";
  62.  version="$VER: "+vers+" (02-Apr-92)";
  63.  
  64.  
  65.  
  66. TYPE
  67.  StringT=ARRAY[0..strlen] OF CHAR;
  68.  StringTPtr=POINTER TO StringT;
  69.  
  70.  
  71.  File=
  72.   RECORD
  73.    fh: DosD.FileHandlePtr;
  74.    eof: BOOLEAN;
  75.   END;
  76.  
  77.  
  78.  
  79.  CostPtr=POINTER TO Cost;
  80.  Cost=
  81.   RECORD
  82.    next: CostPtr;
  83.    start: LONGINT;      (* start time for mpu and unit                  *)
  84.    stop: LONGINT;       (* end time for mpu and unit                    *)
  85.    mpu: REAL;           (* price per unit                               *)
  86.    unit: REAL;          (* unit duration                                *)
  87.   END;
  88.  
  89.  
  90.  
  91.  XSDataPtr=POINTER TO XSData;
  92.  XSData=
  93.   RECORD
  94.    next: XSDataPtr;
  95.    host: StringT;       (* host name                                    *)
  96.    costlist: CostPtr;   (* list of Cost records                         *)
  97.   END;
  98.  
  99.  
  100.  
  101.  Connect=
  102.   RECORD
  103.    host: StringT;               (* host name *)
  104.    callout: BOOLEAN;            (* TRUE: outgoing call *)
  105.    start: LONGINT;              (* session start time *)
  106.    stop: LONGINT;               (* session end time *)
  107.    sdate: DosD.Date;            (* session start time in dos format *)
  108.    inbb: LONGINT;               (* brutto bytes in *)
  109.    outbb: LONGINT;              (* brutto bytes out *)
  110.    inbn: LONGINT;               (* netto bytes in *)
  111.    outbn: LONGINT;              (* netto bytes out *)
  112.    cost: REAL;                  (* connection phone costs *)
  113.    units: LONGINT;              (* phone units consumed *)
  114.    seconds: LONGINT;            (* online time in seconds *)
  115.   END;
  116.  
  117.  
  118.  
  119.   Statistics=
  120.    RECORD
  121.     connects: LONGINT;
  122.     inbb: LONGINT;
  123.     outbb: LONGINT;
  124.     inbn: LONGINT;
  125.     outbn: LONGINT;
  126.     cost: REAL;
  127.     units: LONGINT;
  128.     seconds: LONGINT;
  129.     bpeak: LONGINT;
  130.     npeak: LONGINT;
  131.    END;
  132.  
  133.  
  134.  
  135.   Args=
  136.    RECORD
  137.     xstatdata: StringT;         (* xstat.data name and path;  -d        *)
  138.     xferstat: StringT;          (* xferstat name and path;    -s        *)
  139.     verbose: BOOLEAN;           (* lotsa output;              -v; off   *)
  140.     from: DosD.Date;            (* from date;                 -f; zero  *)
  141.     to: DosD.Date;              (* to date;                   -t; TODAY *)
  142.     host: StringT;              (* for host name only;        -n; off   *)
  143.     in: BOOLEAN;                (* process incoming calls;    -i; on    *)
  144.     out: BOOLEAN;               (* process outgoing calls;    -o; on    *)
  145.     quiet: BOOLEAN;             (* suppress non-fatal errors; -q; off   *)
  146.     monthly: BOOLEAN;           (* monthly mode;              -m; off   *)
  147.    END;
  148.  
  149.  
  150.  
  151. VAR
  152.  inf: File;
  153.  ln1,ln2,temp: StringT;
  154.  log: Connect;
  155.  totali,totalo: Statistics;
  156.  xsroot: XSDataPtr;
  157.  args: Args;
  158.  currency: StringT;
  159.  
  160.  
  161. (*---------------------------------------------------------------------------
  162.   FreeCostChain: FreeMem() a linked list of "Cost" records
  163.   ---------------------------------------------------------------------------*)
  164. PROCEDURE FreeCostChain(d:CostPtr);
  165.  VAR
  166.   n: CostPtr;
  167.  BEGIN
  168.   REPEAT
  169.    n:=d^.next;
  170.    ExecL.FreeMem(d,SIZE(d^));
  171.    d:=n;
  172.   UNTIL n=NIL;
  173.  END FreeCostChain;
  174.  
  175.  
  176.  
  177. (*---------------------------------------------------------------------------
  178.   FreeXData: FreeMem() a linked list of "XSData" records, freeing the
  179.              attached "Cost" record lists as well.
  180.   ---------------------------------------------------------------------------*)
  181. PROCEDURE FreeXData(d: XSDataPtr);
  182.  VAR
  183.   n: XSDataPtr;
  184.  BEGIN
  185.   REPEAT
  186.    n:=d^.next;
  187.    FreeCostChain(d^.costlist);
  188.    ExecL.FreeMem(d,SIZE(d^));
  189.    d:=n;
  190.   UNTIL n=NIL;
  191.  END FreeXData;
  192.  
  193.  
  194.  
  195. (*---------------------------------------------------------------------------
  196.   MyError: Display error messages, terminate program if necessary
  197.   ---------------------------------------------------------------------------*)
  198. (*$ CopyDyn:=FALSE *)
  199. PROCEDURE MyError(x1,x2,x3: ARRAY OF CHAR; fatal: BOOLEAN);
  200.  BEGIN
  201.   IF fatal THEN
  202.    InOut.WriteString("FATAL ERROR ");
  203.    InOut.WriteString(x1); InOut.WriteLn;
  204.    InOut.WriteString(x2); InOut.WriteLn;
  205.    InOut.WriteString(x3); InOut.WriteLn;
  206.    InOut.WriteLn;
  207.    Arts.Exit(20);
  208.   ELSIF (NOT args.quiet) OR ((x1[0]="F") & (x1[1]="A") & (x1[2]="T")) THEN
  209.    InOut.WriteString(x1); InOut.WriteLn;
  210.    InOut.WriteString(x2); InOut.WriteLn;
  211.    InOut.WriteString(x3); InOut.WriteLn;
  212.    InOut.WriteLn;
  213.   END;
  214.  END MyError;
  215.  
  216.  
  217.  
  218. (*---------------------------------------------------------------------------
  219.   GetHead: Remove first char from a string, return it in char variable
  220.   ---------------------------------------------------------------------------*)
  221. PROCEDURE GetHead(VAR s: StringT; VAR c: CHAR);
  222.  BEGIN
  223.   c:=s[0];
  224.   String.DeleteChar(s,0);
  225.  END GetHead;
  226.  
  227.  
  228.  
  229. (*---------------------------------------------------------------------------
  230.   KillSpaces: Remove leading spaces from a string
  231.   ---------------------------------------------------------------------------*)
  232. PROCEDURE KillSpaces(VAR l1: StringT);
  233.  VAR
  234.   c: CHAR;
  235.  BEGIN
  236.   WHILE l1[0]=" " DO
  237.    GetHead(l1,c);
  238.   END;
  239.  END KillSpaces;
  240.  
  241.  
  242.  
  243. (*---------------------------------------------------------------------------
  244.   Get: Remove first "word" from a string. "Word" means "a sequence of
  245.        non-spaces, terminated by at least one space". Return this word
  246.        as a separate string
  247.   ---------------------------------------------------------------------------*)
  248. PROCEDURE Get(VAR s1,s2: StringT): BOOLEAN;
  249.  VAR
  250.   c: CHAR;
  251.  BEGIN
  252.   s2[0]:=ASCII.nul;
  253.   KillSpaces(s1);
  254.   GetHead(s1,c);
  255.   WHILE (c#" ") AND (c#ASCII.nul) DO
  256.    Break.TestBreak();
  257.    String.ConcatChar(s2,c);
  258.    GetHead(s1,c);
  259.   END;
  260.   RETURN String.Length(s2)>0;
  261.  END Get;
  262.  
  263.  
  264.  
  265. (*---------------------------------------------------------------------------
  266.   ConvDate: Convert ASCII representations of date and time to Dos format
  267.   ---------------------------------------------------------------------------*)
  268. (*$ CopyDyn:=FALSE *)
  269. PROCEDURE ConvDate(ds,ts: StringT; VAR dat: DosD.Date): BOOLEAN;
  270.  VAR
  271.   dt: DosD.DateTime;
  272.   res: LONGINT;
  273.  BEGIN
  274.   dt.format:=DosD.formatDOS;
  275.   dt.flags:=DosD.DateTimeFlagSet{};
  276.   dt.strDate:=SYSTEM.ADR(ds);
  277.   dt.strTime:=SYSTEM.ADR(ts);
  278.   res:=DosL.StrToDate(SYSTEM.ADR(dt));
  279.   dat:=dt.date;
  280.   RETURN res=0;
  281.  END ConvDate;
  282.  
  283.  
  284.  
  285. (*---------------------------------------------------------------------------
  286.   ParseArg: Parse argument string (single argument) and set "args" record
  287.             accordingly
  288.   ---------------------------------------------------------------------------*)
  289. PROCEDURE ParseArg(VAR s: StringT): BOOLEAN;
  290.  VAR
  291.   c: CHAR;
  292.   s1,ts: StringT;
  293.  BEGIN
  294.   s1:=s;
  295.   GetHead(s,c);
  296.   IF c#"-" THEN
  297.    MyError("FATAL ERROR","illegal argument",s1,FALSE);
  298.    RETURN TRUE;
  299.   ELSE
  300.    GetHead(s,c);
  301.    CASE c OF
  302.     |"d","D": args.xstatdata:=s;
  303.     |"s","S": args.xferstat:=s;
  304.     |"v","V": args.verbose:=TRUE;
  305.     |"f","F": IF NOT args.monthly THEN
  306.                IF ConvDate(s,"00:00:00",args.from) THEN
  307.                 MyError("FATAL ERROR","illegal FROM date",s1,FALSE);
  308.                 RETURN TRUE;
  309.                END;
  310.               END;
  311.     |"t","T": IF NOT args.monthly THEN
  312.                IF ConvDate(s,"23:59:59",args.to) THEN
  313.                 MyError("FATAL ERROR","illegal TO date",s1,FALSE);
  314.                 RETURN TRUE;
  315.                END;
  316.               END;
  317.     |"n","N": args.host:=s;
  318.     |"i","I": args.in:=FALSE;
  319.     |"o","O": args.out:=FALSE;
  320.     |"q","Q": args.quiet:=TRUE;
  321.     |"m","M": ts:="01-";
  322.               String.Concat(ts,s);
  323.               IF ConvDate(ts,"00:00:00",args.from) THEN
  324.                MyError("FATAL ERROR","illegal MONTH date",s1,FALSE);
  325.                RETURN TRUE;
  326.               END;
  327.               ts:="31-";
  328.               String.Concat(ts,s);
  329.               IF ConvDate(ts,"23:59:59",args.to) THEN
  330.                ts:="30-";
  331.                String.Concat(ts,s);
  332.                IF ConvDate(ts,"23:59:59",args.to) THEN
  333.                 ts:="29-";
  334.                 String.Concat(ts,s);
  335.                 IF ConvDate(ts,"23:59:59",args.to) THEN
  336.                  ts:="28-";
  337.                  String.Concat(ts,s);
  338.                  IF ConvDate(ts,"23:59:59",args.to) THEN
  339.                   MyError("FATAL ERROR","illegal MONTH date",s1,FALSE);
  340.                   RETURN TRUE;
  341.                  END;
  342.                 END;
  343.                END;
  344.               END;
  345.               args.monthly:=TRUE;
  346.     |ELSE     MyError("FATAL ERROR","unknown argument",s1,FALSE);
  347.               RETURN TRUE;
  348.    END;
  349.   END;
  350.   RETURN FALSE;
  351.  END ParseArg;
  352.  
  353.  
  354.  
  355. (*---------------------------------------------------------------------------
  356.   SplitArgs: Split multi-argument string into single arguments and feed
  357.              them through ParseArg()
  358.   ---------------------------------------------------------------------------*)
  359. PROCEDURE SplitArgs(VAR s: StringT);
  360.  VAR
  361.   str: StringT;
  362.   c: CHAR;
  363.   argerr,quotes: BOOLEAN;
  364.  BEGIN
  365.   argerr:=FALSE;
  366.   REPEAT
  367.    quotes:=FALSE;
  368.    str[0]:=ASCII.nul;
  369.    KillSpaces(s);
  370.    WHILE ((s[0]#" ") OR quotes) AND (s[0]#ASCII.nul) DO
  371.     GetHead(s,c);
  372.     IF c='"' THEN
  373.      quotes:=NOT quotes;
  374.     ELSE
  375.      String.ConcatChar(str,c);
  376.     END;
  377.    END;
  378.    IF quotes THEN
  379.     MyError("FATAL ERROR in command line",
  380.             "non-terminated quotes",str,FALSE);
  381.     argerr:=TRUE;
  382.    ELSE
  383.     IF str[0]#ASCII.nul THEN
  384.      argerr:=argerr OR ParseArg(str);
  385.     END;
  386.    END;
  387.   UNTIL s[0]=ASCII.nul;
  388.   IF argerr THEN
  389.    Arts.Exit(20);
  390.   END;
  391.  END SplitArgs;
  392.  
  393.  
  394.  
  395. (*---------------------------------------------------------------------------
  396.   PresetArgs: Set default values for "args" record
  397.   ---------------------------------------------------------------------------*)
  398. PROCEDURE PresetArgs();
  399.  VAR
  400.   bdummy: BOOLEAN;
  401.  BEGIN
  402.   args.xstatdata:=defxstatdata;
  403.   args.xferstat:=defxferstat;
  404.   args.verbose:=FALSE;
  405.   bdummy:=ConvDate("01-JAN-78","00:00:00",args.from);
  406.   bdummy:=ConvDate("TODAY","23:59:59",args.to);
  407.   args.host[0]:=ASCII.nul;
  408.   args.in:=TRUE;
  409.   args.out:=TRUE;
  410.   args.quiet:=FALSE;
  411.   args.monthly:=FALSE;
  412.  END PresetArgs;
  413.  
  414.  
  415.  
  416. (*---------------------------------------------------------------------------
  417.   GetLine: Read a line from the input file
  418.   ---------------------------------------------------------------------------*)
  419. PROCEDURE GetLine(VAR line: StringT);
  420.  VAR
  421.   res: LONGINT;
  422.  BEGIN
  423.   IF inf.fh#NIL THEN
  424.    res:=DosL.FGets(inf.fh,SYSTEM.ADR(line),strlen);
  425.    inf.eof:=(res=0) AND (DosL.IoErr()=0);
  426.   END;
  427.   IF (String.Length(line)>0) AND (line[String.Length(line)-1]=ASCII.lf) THEN
  428.    line[String.Length(line)-1]:=ASCII.nul;
  429.   END;
  430.  END GetLine;
  431.  
  432.  
  433.  
  434. (*---------------------------------------------------------------------------
  435.   GetLog: Read a two-line log entry from Xferstat
  436.   ---------------------------------------------------------------------------*)
  437. PROCEDURE GetLog(VAR l1,l2: StringT);
  438.  BEGIN
  439.   l1[0]:=ASCII.nul;
  440.   l2[0]:=ASCII.nul;
  441.   REPEAT
  442.    Break.TestBreak();
  443.    GetLine(l1);
  444.   UNTIL (inf.eof) OR (l1[0]="<") OR (l1[0]=">");
  445.   IF NOT inf.eof THEN
  446.    GetLine(l2);
  447.   END;
  448.  END GetLog;
  449.  
  450.  
  451.  
  452. (*---------------------------------------------------------------------------
  453.   Skip: Skip the first "word" of a string; refer to "Get()" for an
  454.         explanation ot the term "word"
  455.   ---------------------------------------------------------------------------*)
  456. PROCEDURE Skip(VAR s: StringT);
  457.  VAR
  458.   dum: BOOLEAN;
  459.   x: StringT;
  460.  BEGIN
  461.   dum:=Get(s,x);
  462.  END Skip;
  463.  
  464.  
  465.  
  466. (*---------------------------------------------------------------------------
  467.   GetLNum: Get the first "word" of a string, and convert it to a LONGINT
  468.            (if possible). Refer to "Get()" for an explanation of the
  469.            term "word"
  470.   ---------------------------------------------------------------------------*)
  471. PROCEDURE GetLNum(VAR s: StringT; VAR x: LONGINT): BOOLEAN;
  472.  VAR
  473.   sn: StringT;
  474.   res: LONGINT;
  475.  BEGIN
  476.   IF Get(s,sn) THEN
  477.    res:=DosL.StrToLong(SYSTEM.ADR(sn),x);
  478.    RETURN res>0;
  479.   END;
  480.   RETURN FALSE;
  481.  END GetLNum;
  482.  
  483.  
  484.  
  485. (*---------------------------------------------------------------------------
  486.   GetNum: Call "GetLNum()" and make sure the result is an INTEGER
  487.   ---------------------------------------------------------------------------*)
  488. PROCEDURE GetNum(VAR s: StringT; VAR x: INTEGER): BOOLEAN;
  489.  VAR
  490.   l: LONGINT;
  491.  BEGIN
  492.   IF GetLNum(s,l) THEN
  493.    IF l>MAX(INTEGER) THEN
  494.     x:=0;
  495.    ELSE
  496.     x:=INTEGER(l);
  497.     RETURN TRUE;
  498.    END;
  499.   ELSE
  500.    x:=0;
  501.   END;
  502.   RETURN FALSE;
  503.  END GetNum;
  504.  
  505.  
  506.  
  507. (*---------------------------------------------------------------------------
  508.   GetReal: Get the first "word" of a string, and convert it to a REAL
  509.            (if possible). Refer to "Get()" for an explanation of the
  510.            term "word"
  511.   ---------------------------------------------------------------------------*)
  512. PROCEDURE GetReal(VAR s: StringT; VAR x: REAL): BOOLEAN;
  513.  VAR
  514.   sn: StringT;
  515.   err: BOOLEAN;
  516.  BEGIN
  517.   IF Get(s,sn) THEN
  518.    RealConversions.StrToReal(sn,x,err);
  519.    RETURN NOT err;
  520.   END;
  521.   RETURN FALSE;
  522.  END GetReal;
  523.  
  524.  
  525.  
  526. (*---------------------------------------------------------------------------
  527.   GetTime: Get the first two "words" of a string, and convert them to
  528.            Dos format date and time (if possible). Refer to "Get()" for an
  529.            explanation of the term "word"
  530.   ---------------------------------------------------------------------------*)
  531. PROCEDURE GetTime(VAR s: StringT; VAR t: LONGINT; VAR dos: DosD.Date): BOOLEAN;
  532.  VAR
  533.   ds,ts: StringT;
  534.   dt: DosD.DateTime;
  535.  BEGIN
  536.   IF Get(s,ds) THEN
  537.    IF Get(s,ts) THEN
  538.     dt.format:=DosD.formatCDN;
  539.     dt.flags:=DosD.DateTimeFlagSet{};
  540.     dt.strDate:=SYSTEM.ADR(ds);
  541.     dt.strTime:=SYSTEM.ADR(ts);
  542.  
  543.     IF DosL.StrToDate(SYSTEM.ADR(dt))#0 THEN
  544.      dos:=dt.date;
  545.      t:=(dos.days MOD 7)*dmult+(dos.minute*mmult)+(dos.tick DIV 50);
  546.      RETURN TRUE;
  547.     END;
  548.    END;
  549.   END;
  550.   RETURN FALSE;
  551.  END GetTime;
  552.  
  553.  
  554.  
  555. (*---------------------------------------------------------------------------
  556.   TestXStatHeader: Check if the input file's header is "H XSTAT DATA"
  557.   ---------------------------------------------------------------------------*)
  558. PROCEDURE TestXStatHeader();
  559.  VAR
  560.   s1,s,t: StringT;
  561.   c: CHAR;
  562.  BEGIN
  563.   GetLine(s);
  564.   s1:=s;
  565.   GetHead(s,c);
  566.   IF c="H" THEN
  567.    IF Get(s,t) OR (String.Compare(t,"XSTAT")#0) THEN
  568.     IF NOT (Get(s,t) OR (String.Compare(t,"DATA")#0)) THEN
  569.      MyError("in XStat.data header",
  570.              s1,"keyword DATA not found",TRUE);
  571.     END;
  572.    ELSE
  573.     MyError("in XStat.data header",
  574.             s1,"keyword XSTAT not found",TRUE);
  575.    END;
  576.   ELSE
  577.    MyError("in XStat.data header",
  578.            s1,"header identifier H not found",TRUE);
  579.   END;
  580.  END TestXStatHeader;
  581.  
  582.  
  583.  
  584. (*---------------------------------------------------------------------------
  585.   ParseXSDataNEntry: Parse a "host name" line from XStat.data
  586.   ---------------------------------------------------------------------------*)
  587. PROCEDURE ParseXSDataNEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  588.  VAR
  589.   xsd: XSData;
  590.   t: StringT;
  591.  BEGIN
  592.   IF (cxsd#NIL) AND (cxsd^.costlist=NIL) THEN
  593.    MyError("in XStat.data",
  594.            "N-line without subsequent C-lines for",
  595.            cxsd^.host,TRUE);
  596.   END;
  597.  
  598.   IF Get(s,t) THEN
  599.    xsd.next:=NIL;
  600.    xsd.host:=t;
  601.    xsd.costlist:=NIL;
  602.    cxsd:=xsroot;
  603.    IF xsroot#NIL THEN
  604.     WHILE cxsd^.next#NIL DO
  605.      cxsd:=cxsd^.next;
  606.     END;
  607.     cxsd^.next:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  608.     IF cxsd^.next=NIL THEN
  609.      MyError("","not enough memory","",TRUE);
  610.     END;
  611.     cxsd:=cxsd^.next;
  612.    ELSE
  613.     xsroot:=ExecL.AllocMem(SIZE(xsd),ExecD.MemReqSet{});
  614.     IF xsroot=NIL THEN
  615.      MyError("","not enough memory","",TRUE);
  616.     END;
  617.     cxsd:=xsroot;
  618.    END;
  619.    cxsd^:=xsd;
  620.   ELSE
  621.    MyError("in XStat.data",
  622.            s1,"no host name found in N-line",TRUE);
  623.   END;
  624.  END ParseXSDataNEntry;
  625.  
  626.  
  627.  
  628. (*---------------------------------------------------------------------------
  629.   ParseXSDataCEntry: Parse a "cost info" line from XStat.data
  630.   ---------------------------------------------------------------------------*)
  631. PROCEDURE ParseXSDataCEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  632.  VAR
  633.   t,t1: StringT;
  634.   cost: Cost;
  635.   ccp: CostPtr;
  636.   temp: INTEGER;
  637.   c0{R.D7},c1{R.D6}: CHAR;
  638.  BEGIN
  639.   IF cxsd#NIL THEN
  640.    cost.next:=NIL;
  641.    cost.stop:=7*dmult-1;
  642.    IF Get(s,t) AND
  643.       (t[2]="-") AND (t[5]=":") AND (t[8]=":") AND
  644.       (String.Length(t)=11) THEN
  645.     c0:=t[0]; c1:=t[1];
  646.     IF (c0="S") AND (c1="U") THEN
  647.      cost.start:=dsun;
  648.     ELSIF (c0="M") AND (c1="O") THEN
  649.      cost.start:=dmon;
  650.     ELSIF (c0="T") AND (c1="U") THEN
  651.      cost.start:=dtue;
  652.     ELSIF (c0="W") AND (c1="E") THEN
  653.      cost.start:=dwed;
  654.     ELSIF (c0="T") AND (c1="H") THEN
  655.      cost.start:=dthu;
  656.     ELSIF (c0="F") AND (c1="R") THEN
  657.      cost.start:=dfri;
  658.     ELSIF (c0="S") AND (c1="A") THEN
  659.      cost.start:=dsat;
  660.     ELSE
  661.      MyError("in XStat.data",
  662.              s1,"illegal day of week in start time in C-line",TRUE);
  663.     END;
  664.     t[0]:=" "; t[1]:=" "; t[2]:=" "; t[5]:=" "; t[8]:=" ";
  665.     IF GetNum(t,temp) THEN
  666.      cost.start:=cost.start+hmult*LONGINT(temp);
  667.      IF GetNum(t,temp) THEN
  668.       cost.start:=cost.start+mmult*LONGINT(temp);
  669.       IF GetNum(t,temp) THEN
  670.        cost.start:=cost.start+smult*LONGINT(temp);
  671.        IF GetReal(s,cost.unit) THEN
  672.         IF GetReal(s,cost.mpu) THEN
  673.          ccp:=cxsd^.costlist;
  674.          IF ccp#NIL THEN
  675.           WHILE ccp^.next#NIL DO
  676.            Break.TestBreak();
  677.            ccp:=ccp^.next;
  678.           END;
  679.           ccp^.next:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  680.           IF ccp^.next=NIL THEN
  681.            MyError("","not enough memory","",TRUE);
  682.           END;
  683.           ccp^.next^:=cost;
  684.           ccp^.stop:=cost.start-1;
  685.           IF ccp^.stop<ccp^.start THEN
  686.            MyError("in XStat.data","connect start time sequence error",
  687.                    "(you probably swapped some C-lines, or you left out an N-line)",
  688.                    TRUE);
  689.           END;
  690.          ELSE
  691.           cxsd^.costlist:=ExecL.AllocMem(SIZE(Cost),ExecD.MemReqSet{});
  692.           IF cxsd^.costlist=NIL THEN
  693.            MyError("","not enough memory","",TRUE);
  694.           END;
  695.           cxsd^.costlist^:=cost;
  696.           IF cost.start#0 THEN
  697.            MyError("in XStat.data",
  698.                    s1,"start time in first C-line must be SU-00:00:00",TRUE);
  699.           END;
  700.          END;
  701.         ELSE
  702.          MyError("in XStat.data",
  703.                  s1,"illegal 'money per unit' in C-line",TRUE);
  704.         END;
  705.        ELSE
  706.         MyError("in XStat.data",
  707.                 s1,"illegal 'seconds per unit' in C-line",TRUE);
  708.        END;
  709.       ELSE
  710.        MyError("in XStat.data",
  711.                s1,"illegal 'seconds' part in start time in C-line",TRUE);
  712.       END;
  713.      ELSE
  714.       MyError("in XStat.data",
  715.               s1,"illegal 'minutes' part in start time in C-line",TRUE);
  716.      END;
  717.     ELSE
  718.      MyError("in XStat.data",
  719.              s1,"illegal 'hours' part in start time in C-line",TRUE);
  720.     END;
  721.    ELSE
  722.     MyError("in XStat.data",
  723.             s1,"illegal or missing start time in C-line",TRUE);
  724.    END;
  725.   ELSE
  726.    MyError("in XStat.data",
  727.            s1,"C-line without corresponding N-line",TRUE);
  728.   END;
  729.  END ParseXSDataCEntry;
  730.  
  731.  
  732.  
  733. (*---------------------------------------------------------------------------
  734.   ParseXSDataSEntry: Parse a "currency sign info" line from XStat.data
  735.   ---------------------------------------------------------------------------*)
  736. PROCEDURE ParseXSDataSEntry(VAR s,s1: StringT; VAR cxsd: XSDataPtr);
  737.  VAR
  738.   t: StringT;
  739.  BEGIN
  740.   IF (cxsd#NIL) THEN
  741.    MyError("in XStat.data",
  742.            s1,"S-line after the first connection cost record",TRUE);
  743.   END;
  744.  
  745.   IF Get(s,t) THEN
  746.    IF currency[0]=ASCII.nul THEN
  747.     currency:=t;
  748.     IF currency[0]#" " THEN
  749.      String.Insert(currency,0," \o");
  750.     END;
  751.    ELSE
  752.     MyError("in XStat.data",
  753.             s1,"duplicate S-line found",TRUE);
  754.    END;
  755.   ELSE
  756.    MyError("in XStat.data",
  757.            s1,"missing currency sign in S-line",TRUE);
  758.   END;
  759.  END ParseXSDataSEntry;
  760.  
  761.  
  762.  
  763. (*---------------------------------------------------------------------------
  764.   GetXStatData: Read and parse the XStat.data file
  765.   ---------------------------------------------------------------------------*)
  766. PROCEDURE GetXStatData();
  767.  VAR
  768.   s1,s: StringT;
  769.   c: CHAR;
  770.   cxsd: XSDataPtr;
  771.  BEGIN
  772.   cxsd:=NIL;
  773.   TestXStatHeader();
  774.   REPEAT
  775.    Break.TestBreak();
  776.    GetLine(s);
  777.    s1:=s;
  778.    GetHead(s,c);
  779.    CASE c OF
  780.     |"#": (* NOP *)
  781.     |"N": ParseXSDataNEntry(s,s1,cxsd);
  782.     |"C": ParseXSDataCEntry(s,s1,cxsd);
  783.     |"S": ParseXSDataSEntry(s,s1,cxsd);
  784.     |ELSE (* unknown: NOP *)
  785.    END;
  786.   UNTIL inf.eof;
  787.  END GetXStatData;
  788.  
  789.  
  790.  
  791. (*---------------------------------------------------------------------------
  792.   ParseLog: Parse a two-line connection data entry from Xferstat
  793.   ---------------------------------------------------------------------------*)
  794. (*$ CopyDyn:=FALSE *)
  795. PROCEDURE ParseLog(line1,line2: StringT; VAR log: Connect): BOOLEAN;
  796.  VAR
  797.   c: CHAR;
  798.   l1,l2: StringT;
  799.   dummy: DosD.Date;
  800.  BEGIN
  801.   l1:=line1;
  802.   l2:=line2;
  803.   GetHead(l1,c);
  804.   log.callout:=(c="<");
  805.  
  806.   IF Get(l1,log.host) THEN
  807.    IF (log.host[0]>="0") AND (log.host[0]<="9") THEN
  808.     MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  809.     RETURN FALSE;
  810.    END;
  811.    IF GetTime(l1,log.start,log.sdate) THEN
  812.     Skip(l1);
  813.     IF GetTime(l1,log.stop,dummy) THEN
  814.      GetHead(l2,c);
  815.      IF c="|" THEN
  816.       Skip(l2);
  817.       Skip(l2);
  818.       Skip(l2);
  819.       IF GetLNum(l2,log.inbb) THEN
  820.        IF GetLNum(l2,log.outbb) THEN
  821.         Skip(l2);
  822.         IF GetLNum(l2,log.inbn) THEN
  823.          IF GetLNum(l2,log.outbn) THEN
  824.           RETURN TRUE;
  825.          ELSE
  826.           MyError("invalid log entry: can't get netto send bytes",line1,line2,FALSE);
  827.          END;
  828.         ELSE
  829.          MyError("invalid log entry: can't get netto receive bytes",line1,line2,FALSE);
  830.         END;
  831.        ELSE
  832.         MyError("invalid log entry: can't get brutto send bytes",line1,line2,FALSE);
  833.        END;
  834.       ELSE
  835.        MyError("invalid log entry: can't get brutto receive bytes",line1,line2,FALSE);
  836.       END;
  837.      ELSE
  838.       MyError("invalid log entry: illegal continuation line header ",line1,line2,FALSE);
  839.      END;
  840.     ELSE
  841.      MyError("invalid log entry: can't compute end time",line1,line2,FALSE);
  842.     END;
  843.    ELSE
  844.     MyError("invalid log entry: can't compute start time",line1,line2,FALSE);
  845.    END;
  846.   ELSE
  847.    MyError("invalid log entry: can't find host name",line1,line2,FALSE);
  848.   END;
  849.   RETURN FALSE;
  850.  END ParseLog;
  851.  
  852.  
  853.  
  854. (*---------------------------------------------------------------------------
  855.   ComputeCost: Compute the cost for a single connect
  856.   ---------------------------------------------------------------------------*)
  857. PROCEDURE ComputeCost(VAR log: Connect): BOOLEAN;
  858.  VAR
  859.   cxsd: XSDataPtr;
  860.   ccost: CostPtr;
  861.   time: REAL;
  862.  BEGIN
  863.   log.seconds:=log.stop-log.start+1;
  864.   IF log.seconds<0 THEN
  865.    log.seconds:=log.seconds+7*dmult;
  866.   END;
  867.  
  868.   cxsd:=xsroot;
  869.   WHILE (cxsd#NIL) AND (String.Compare(cxsd^.host,log.host)#0) DO
  870.    Break.TestBreak();
  871.    cxsd:=cxsd^.next;
  872.   END;
  873.   IF cxsd=NIL THEN
  874.    MyError("ERROR: no connection cost data found for host",log.host,
  875.            "extend your XStat.data file!",FALSE);
  876.    RETURN FALSE;
  877.   END;
  878.  
  879.   ccost:=cxsd^.costlist;
  880.   WHILE log.start>ccost^.stop DO
  881.    Break.TestBreak();
  882.    ccost:=ccost^.next;
  883.   END;
  884.  
  885.   IF log.stop<log.start THEN
  886.    log.stop:=log.stop+7*dmult;
  887.   END;
  888.   log.cost:=0.0;
  889.   log.units:=0;
  890.   time:=REAL(log.start);
  891.   REPEAT
  892.    Break.TestBreak();
  893.    log.cost:=log.cost+ccost^.mpu;
  894.    time:=time+ccost^.unit;
  895.    INC(log.units);
  896.  
  897.    IF LONGINT(time)>ccost^.stop THEN
  898.     ccost:=ccost^.next;
  899.     IF ccost=NIL THEN
  900.      ccost:=cxsd^.costlist;
  901.      time:=time-REAL(7*dmult);
  902.      log.stop:=log.stop-7*dmult;
  903.     END;
  904.    END;
  905.   UNTIL LONGINT(time)>log.stop;
  906.  
  907.   RETURN TRUE;
  908.  END ComputeCost;
  909.  
  910.  
  911.  
  912. (*---------------------------------------------------------------------------
  913.   DisplayStats: Display the statistics for a single connect
  914.   ---------------------------------------------------------------------------*)
  915. PROCEDURE DisplayStats(log: Connect);
  916.  VAR
  917.   name: StringT;
  918.  BEGIN
  919.   name:=log.host;
  920.   WHILE String.Length(name)<8 DO
  921.    String.Insert(name,0," \o");
  922.   END;
  923.   InOut.WriteString("\nhost name\t"); InOut.WriteString(name);
  924.   InOut.WriteString("\ndirection\t     "); IF log.callout THEN
  925.                                             InOut.WriteString("out");
  926.                                            ELSE
  927.                                             InOut.WriteString(" in");
  928.                                            END;
  929.   InOut.WriteString("\nonline time\t"); InOut.WriteInt(log.seconds,8); InOut.WriteString(" seconds");
  930.   InOut.WriteString("\nunits\t\t"); InOut.WriteInt(log.units,8); InOut.WriteString(" units");
  931.   InOut.WriteString("\ncost\t\t"); RealInOut.WriteReal(log.cost,8,2); InOut.Write(" "); InOut.WriteString(currency);
  932.   InOut.WriteString("\n\n\n\n");
  933.  END DisplayStats;
  934.  
  935.  
  936.  
  937. (*---------------------------------------------------------------------------
  938.   DisplayTotals: Display the overall statistics
  939.   ---------------------------------------------------------------------------*)
  940. PROCEDURE DisplayTotals(tot: Statistics; callout: BOOLEAN);
  941.  BEGIN
  942.   InOut.WriteString("Connection statistics for ");
  943.   IF callout THEN
  944.    InOut.WriteString("outgoing");
  945.   ELSE
  946.    InOut.WriteString("incoming");
  947.   END;
  948.   InOut.WriteString(" calls:\n");
  949.   InOut.WriteString("-----------------------------------------\n");
  950.  
  951.   IF tot.connects=0 THEN
  952.    InOut.WriteString("\nno connects recorded.\n\n\n\n");
  953.   ELSE
  954.    InOut.WriteString("\nconnects        "); InOut.WriteInt(tot.connects,8);
  955.    InOut.WriteString("\nonline time     "); InOut.WriteInt(tot.seconds,8); InOut.WriteString(" sec\t  ("); InOut.WriteInt((tot.seconds+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" sec/connect)");
  956.    InOut.WriteString("\nunits           "); InOut.WriteInt(tot.units,8); InOut.WriteString(" units\t  ("); RealInOut.WriteReal(REAL(tot.units)/REAL(tot.connects),8,3); InOut.WriteString(" units/connect)");
  957.    InOut.WriteString("\ncost            "); RealInOut.WriteReal(tot.cost,8,2); InOut.WriteString(currency); InOut.WriteString("\t  ("); RealInOut.WriteReal(tot.cost/REAL(tot.connects),8,3); InOut.WriteString(currency); InOut.WriteString("/connect)");
  958.  InOut.WriteString("\n\nbrutto read     "); InOut.WriteInt(tot.inbb,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.inbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  959.    InOut.WriteString("\nbrutto send     "); InOut.WriteInt(tot.outbb,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.outbb+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  960.    InOut.WriteString("\nnetto read      "); InOut.WriteInt(tot.inbn,8); InOut.WriteString(" bytes\t  ("); InOut.WriteInt((tot.inbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)");
  961.    InOut.WriteString("\nnetto send      "); InOut.WriteInt(tot.outbn,8); InOut.WriteString(" bytes\t  (");  InOut.WriteInt((tot.outbn+tot.connects-1) DIV tot.connects,8); InOut.WriteString(" bytes/connect)\n");
  962.    InOut.WriteString("\nø brutto speed  "); InOut.WriteInt((tot.inbb+tot.outbb) DIV tot.seconds,8); InOut.WriteString(" cps\t  ("); InOut.WriteInt(tot.bpeak,8); InOut.WriteString(" cps peak)");
  963.    InOut.WriteString("\nø netto speed   "); InOut.WriteInt((tot.inbn+tot.outbn) DIV tot.seconds,8); InOut.WriteString(" cps\t  ("); InOut.WriteInt(tot.npeak,8); InOut.WriteString(" cps peak)");
  964.  
  965.    InOut.WriteString("\nø brutto cost   ");
  966.    IF tot.inbb+tot.outbb=0 THEN
  967.     InOut.WriteString("       -");
  968.    ELSE
  969.     RealInOut.WriteReal(tot.cost/REAL(tot.inbb+tot.outbb)*1048576.0,8,3);
  970.    END;
  971.    InOut.WriteString(currency); InOut.WriteString("/MB");
  972.  
  973.    InOut.WriteString("\nø netto cost    ");
  974.    IF tot.inbn+tot.outbn=0 THEN
  975.     InOut.WriteString("       -");
  976.    ELSE
  977.     RealInOut.WriteReal(tot.cost/REAL(tot.inbn+tot.outbn)*1048576.0,8,3);
  978.    END;
  979.    InOut.WriteString(currency); InOut.WriteString("/MB\n\n\n\n");
  980.   END;
  981.  END DisplayTotals;
  982.  
  983.  
  984.  
  985. (*---------------------------------------------------------------------------
  986.   UpdateTotals: Update the totali/totalo records
  987.   ---------------------------------------------------------------------------*)
  988. PROCEDURE UpdateTotals(VAR tot: Statistics; VAR log: Connect);
  989.  VAR
  990.   tempspeed: LONGINT;
  991.  BEGIN
  992.   INC(tot.connects);
  993.   tot.seconds:=tot.seconds+log.seconds;
  994.   tot.inbb:=tot.inbb+log.inbb;
  995.   tot.outbb:=tot.outbb+log.outbb;
  996.   tot.inbn:=tot.inbn+log.inbn;
  997.   tot.outbn:=tot.outbn+log.outbn;
  998.   tot.units:=tot.units+log.units;
  999.   tot.cost:=tot.cost+log.cost;
  1000.  
  1001.   IF log.seconds#0 THEN
  1002.    tempspeed:=(log.inbb+log.outbb) DIV log.seconds;
  1003.    IF tempspeed>tot.bpeak THEN tot.bpeak:=tempspeed END;
  1004.  
  1005.    tempspeed:=(log.inbn+log.outbn) DIV log.seconds;
  1006.    IF tempspeed>tot.npeak THEN tot.npeak:=tempspeed END;
  1007.   END;
  1008.  END UpdateTotals;
  1009.  
  1010.  
  1011.  
  1012. BEGIN
  1013.  SYSTEM.SETREG(11,SYSTEM.ADR(version));
  1014.  xsroot:=NIL;
  1015.  inf.fh:=NIL;
  1016.  
  1017.  IF (Arts.kickVersion<36) OR (DosL.dosVersion<36) THEN
  1018.   MyError("","You need OS 2.0 or later (dos.library V36 or later)",
  1019.           "",TRUE);
  1020.  END;
  1021.  
  1022.  Break.InstallException;
  1023.  PresetArgs();
  1024.  
  1025.  InOut.WriteString("\n\n"+vers+"\n");
  1026.  InOut.WriteString("© Copyright 1992 by Jürgen Weinelt\n");
  1027.  InOut.WriteString("XStat is Freeware - read the docs for details.\n\n\n");
  1028.  
  1029.  IF NOT Arts.wbStarted THEN
  1030.   IF Arts.dosCmdLen>0 THEN
  1031.    temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1032.    IF (temp[0]="?") OR ((temp[0]="-") AND
  1033.                         (temp[1]="?") OR (temp[1]="h") OR (temp[1]="H")) THEN
  1034.     temp:=SYSTEM.CAST(StringTPtr,Arts.programName)^;
  1035.     InOut.WriteString(temp);
  1036.     InOut.WriteString(" [?|-?|-h] [-dname] [-sname] [-v] [-fdate] [-tdate] [-i] [-o] [-nname]\n\n");
  1037.  
  1038.     InOut.WriteString(" -dname   XStat.data file            def="); InOut.WriteString(defxstatdata);
  1039.   InOut.WriteString("\n -sname   Xferstat file              def="); InOut.WriteString(defxferstat);
  1040.   InOut.WriteString("\n -v       verbose mode               def=off\n");
  1041.     InOut.WriteString(" -fdate   ignore calls before date   def=01-JAN-78\n");
  1042.     InOut.WriteString(" -tdate   ignore calls after date    def=TODAY\n");
  1043.     InOut.WriteString(" -mmonth  monthly, override -f/-t    def=off (format MMM-YY)\n");
  1044.     InOut.WriteString(" -i       incoming calls             def=on\n");
  1045.     InOut.WriteString(" -o       outgoing calls             def=on\n");
  1046.     InOut.WriteString(" -q       suppress non-fatal errors  def=off\n");
  1047.     InOut.WriteString(" -nname   calls to particular host   def=off\n\n\n\n");
  1048.  
  1049.     Arts.Exit(5);
  1050.    END;
  1051.   END;
  1052.  END;
  1053.  
  1054.  InOut.WriteLn; InOut.WriteLn;
  1055.  
  1056.  IF DosL.GetVar(SYSTEM.ADR("XSTATARGS"),SYSTEM.ADR(temp),
  1057.                 strlen,DosD.VarFlagSet{})#-1 THEN
  1058.   SplitArgs(temp);
  1059.  END;
  1060.  
  1061.  IF NOT Arts.wbStarted THEN
  1062.   IF Arts.dosCmdLen>0 THEN
  1063.    temp:=SYSTEM.CAST(StringTPtr,Arts.dosCmdBuf)^;
  1064.    IF temp[String.Length(temp)-1]=ASCII.lf THEN
  1065.     temp[String.Length(temp)-1]:=ASCII.nul;
  1066.    END;
  1067.    SplitArgs(temp);
  1068.   END;
  1069.  END;
  1070.  
  1071.  IF DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(args.to))<0 THEN
  1072.   MyError("","illegal combination of parameters",
  1073.           "FROM date is later than TO date",TRUE);
  1074.  END;
  1075.  IF NOT (args.in OR args.out) THEN
  1076.   MyError("","illegal combination of parameters",
  1077.           "you can't specify -i and -o simultaneously",TRUE);
  1078.  END;
  1079.  
  1080.  totali.connects:=0;
  1081.  totali.inbb:=0;
  1082.  totali.outbb:=0;
  1083.  totali.inbn:=0;
  1084.  totali.outbn:=0;
  1085.  totali.cost:=0.0;
  1086.  totali.units:=0;
  1087.  totali.seconds:=0;
  1088.  totali.bpeak:=0;
  1089.  totali.npeak:=0;
  1090.  
  1091.  totalo:=totali;
  1092.  
  1093.  inf.fh:=DosL.Open(SYSTEM.ADR(args.xstatdata),DosD.readOnly);
  1094.  IF inf.fh=NIL THEN
  1095.   MyError("Can't open XStat.data!","filename was",args.xstatdata,TRUE);
  1096.  END;
  1097.  GetXStatData();
  1098.  DosL.Close(inf.fh);
  1099.  IF currency[0]=ASCII.nul THEN
  1100.   MyError("WARNING","no currency definition found in XStat.data",
  1101.           "using 'DM' as default",FALSE);
  1102.   currency:=" DM";
  1103.  END;
  1104.  
  1105.  inf.fh:=DosL.Open(SYSTEM.ADR(args.xferstat),DosD.readOnly);
  1106.  IF inf.fh=NIL THEN
  1107.   MyError("Can't open Xferstat!","filename was",args.xferstat,TRUE);
  1108.  END;
  1109.  
  1110.  REPEAT
  1111.   Break.TestBreak();
  1112.   GetLog(ln1,ln2);
  1113.   IF String.Length(ln1)+String.Length(ln2)>0 THEN
  1114.    IF ParseLog(ln1,ln2,log) THEN
  1115.     IF (args.host[0]=ASCII.nul) OR (String.Compare(args.host,log.host)=0) THEN
  1116.      IF (DosL.CompareDates(SYSTEM.ADR(args.from),SYSTEM.ADR(log.sdate))>=0) AND
  1117.         (DosL.CompareDates(SYSTEM.ADR(log.sdate),SYSTEM.ADR(args.to))>=0) THEN
  1118.       IF ComputeCost(log) THEN
  1119.        IF args.verbose THEN
  1120.         DisplayStats(log);
  1121.        END;
  1122.  
  1123.        IF log.callout THEN
  1124.         UpdateTotals(totalo,log);
  1125.        ELSE
  1126.         UpdateTotals(totali,log);
  1127.        END;
  1128.       ELSE
  1129.        IF NOT args.quiet THEN
  1130.         InOut.WriteString("(ignoring this one)\n\n\n\n");
  1131.        END;
  1132.       END;
  1133.      END;
  1134.     END;
  1135.    ELSE
  1136.     IF NOT args.quiet THEN
  1137.      InOut.WriteString("(ignoring this one)\n\n\n\n");
  1138.     END;
  1139.    END;
  1140.   END;
  1141.  UNTIL inf.eof;
  1142.  
  1143.  IF args.out THEN
  1144.   DisplayTotals(totalo,TRUE);
  1145.  END;
  1146.  IF args.in THEN
  1147.   DisplayTotals(totali,FALSE);
  1148.  END;
  1149. CLOSE
  1150.  IF xsroot#NIL THEN
  1151.   FreeXData(xsroot);
  1152.   xsroot:=NIL;
  1153.  END;
  1154.  IF inf.fh#NIL THEN
  1155.   DosL.Close(inf.fh);
  1156.   inf.fh:=NIL;
  1157.  END;
  1158. END XStat.
  1159.